package com.jnc.rest.core.config.mvc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.jnc.rest.biz.common.service.DictService;
import com.jnc.rest.constant.SysConstant;
import com.jnc.rest.core.base.dto.BaseResp;
import com.jnc.rest.core.base.dto.RespCode;
import com.jnc.rest.core.config.redis.RedisStore;
import com.jnc.rest.core.error.AuthException;
import com.jnc.rest.core.error.ServiceException;
import com.jnc.rest.core.interceptor.AccessTokenInterceptor;
import com.jnc.rest.util.TokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.config.annotation.*;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @Author: jjn
 * @Date: 2018/5/8
 * @Desc: spring mvc 配置
 */
@Slf4j
@Configuration
@SuppressWarnings("rawtypes")
public class WebMvcConfig implements WebMvcConfigurer {

    @Resource
    private RedisStore redisStore;
    @Resource
    private TokenUtil tokenUtil;
    @Resource
    private DictService dictService;
    @Value("${token.timeout}")
    private long timeout;

    /**
     * 扩展消息转换器 (使用阿里 FastJson 转换)
     * @param list
     */
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> list) {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        FastJsonConfig config = new FastJsonConfig();
        config.setSerializerFeatures(
                SerializerFeature.WriteMapNullValue,   //保留空的字段
                SerializerFeature.WriteNullStringAsEmpty,  //String null -> ""
                SerializerFeature.WriteNullNumberAsZero    //Number null -> 0
        );
        converter.setFastJsonConfig(config);
        converter.setDefaultCharset(Charset.forName(SysConstant.CHARSET_NAME));
        list.add(converter);
    }

    /**
     * 添加拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //接口签名认证拦截器，
        registry.addInterceptor(new AccessTokenInterceptor(redisStore, tokenUtil, timeout, dictService));
    }

    /**
     * 统一异常处理
     * @param list
     */
    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {
        list.add(new HandlerExceptionResolver() {
            
            @Override
            public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
                BaseResp resp = new BaseResp();
                if(e instanceof ServiceException){ //业务失败的异常，使用ServiceException抛出的异常
                    resp.setCode(400).setMsg(e.getMessage());
                    log.warn(e.getMessage());
                }else if(e instanceof AuthException){
                    resp.setCode(401).setMsg(e.getMessage());
                    log.error("认证失败, error: {}", e);
                }else if(e instanceof IllegalArgumentException){
                    resp.setCode(RespCode.ILLEGAL_ARGUMENT.getCode()).setMsg("非法参数");
                    log.error("非法请求参数, error: {}", e);
                }else if(e instanceof MethodArgumentNotValidException){ //Bean Validation参数@RequestBody方式校验异常
                    MethodArgumentNotValidException me = (MethodArgumentNotValidException) e;
                    resp = filterMethodArgumentNotValidException(me);
                }else if(e instanceof ConstraintViolationException){ //Bean Validation参数@RequestParam方式校验异常
                    ConstraintViolationException ce = (ConstraintViolationException) e;
                    resp = filterConstraintViolationException(ce);
                }else if(e instanceof BindException){ //处理Get请求中 使用@Valid 验证路径中请求实体校验失败后抛出的异常
                    BindException be = (BindException) e;
                    resp = filterBindException(be);
                }else if(e instanceof NoHandlerFoundException){
                    log.error(e.getMessage());
                    resp.setCode(404).setMsg("接口 [" + request.getRequestURI() + "] 不存在");
                }else if(e instanceof ServletException){
                    log.error(e.getMessage());
                    resp.setCode(400).setMsg(e.getMessage());
                }else{
                    log.error(e.getMessage());
                    resp.setCode(500).setMsg("接口 [" + request.getRequestURI() + "] 内部错误，请联系管理员");
                    String message;
                    if(handler instanceof HandlerMethod){
                        HandlerMethod handlerMethod = (HandlerMethod) handler;
                        message = String.format("接口 [%s] 出现异常，方法：%s.%s，异常信息：%s",
                                request.getRequestURI(),
                                handlerMethod.getBean().getClass().getName(),
                                handlerMethod.getMethod().getName(),
                                e.getMessage()
                                );
                    }else{
                        message = e.getMessage();
                    }
                    log.error(message, e);
                }
                responseResult(response, resp);
                return new ModelAndView();
            }
        });
    }

    /**
     * 解决跨域问题
     * allowedOrigins()中的参数代表可以访问该接口的域名，如：http:localhost:8080； “**”号代表所有域名
     * addMapping()中的参数代表可以访问的URI
     * @param corsRegistry
     */
    @Override
    public void addCorsMappings(CorsRegistry corsRegistry) {
        corsRegistry.addMapping("/**")
                .allowedOrigins("*")
                .allowCredentials(true)
                .allowedHeaders("*")
                .allowedMethods("*");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry resourceHandlerRegistry) {

    }

    @Override
    public void configurePathMatch(PathMatchConfigurer pathMatchConfigurer) {

    }

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer contentNegotiationConfigurer) {

    }

    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer asyncSupportConfigurer) {

    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer defaultServletHandlerConfigurer) {

    }

    @Override
    public void addFormatters(FormatterRegistry formatterRegistry) {

    }




    @Override
    public void addViewControllers(ViewControllerRegistry viewControllerRegistry) {

    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry viewResolverRegistry) {

    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> list) {

    }

    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> list) {

    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> list) {

    }



    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {

    }

    @Override
    public Validator getValidator() {
        return null;
    }

    @Override
    public MessageCodesResolver getMessageCodesResolver() {
        return null;
    }




    /**
     * 写出响应数据
     * @param response
     * @param resp
     */
    private void responseResult(HttpServletResponse response, BaseResp resp){
        response.setCharacterEncoding(SysConstant.CHARSET_NAME);
        response.setHeader("Content-type", "application/json;charset=UTF-8");
        response.setStatus(SysConstant.HTTP_CODE_SUCCESS);
        try {
            response.getWriter().write(JSON.toJSONString(resp));
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    /**
     * 过滤Bean Validation参数@RequestBody方式校验的提示信息
     * @return
     */
    private BaseResp filterMethodArgumentNotValidException (MethodArgumentNotValidException e){
        FieldError fieldError = e.getBindingResult().getFieldError();
        log.info("参数校验异常: {}({})", fieldError.getDefaultMessage(),fieldError.getField());
        return BaseResp.resp(RespCode.ILLEGAL_ARGUMENT.getCode(), fieldError.getDefaultMessage(), null);
    }

    /**
     * 过滤处理Get请求中 使用@Valid 验证路径中请求实体校验失败后抛出的异常
     * @param e
     * @return
     */
    private BaseResp filterBindException (BindException e){
        FieldError fieldError = e.getBindingResult().getFieldError();
        log.info("参数校验异常: {}({})", fieldError.getDefaultMessage(),fieldError.getField());
        return BaseResp.resp(RespCode.ILLEGAL_ARGUMENT.getCode(), fieldError.getDefaultMessage(), null);
    }

    /**
     * 过滤Bean Validation参数@RequestParam方式校验的提示信息
     * @param e
     * @return
     */
    private BaseResp filterConstraintViolationException (ConstraintViolationException e){
        String msg = e.getConstraintViolations().stream().map(ConstraintViolation::toString).collect(Collectors.joining());
        log.info("参数校验异常: {}", msg);
        return BaseResp.resp(RespCode.ILLEGAL_ARGUMENT.getCode(), msg, null);
    }

}
